diff options
author | James Gregory <james@james.id.au> | 2013-08-19 14:01:32 -0700 |
---|---|---|
committer | James Gregory <james@james.id.au> | 2013-08-19 14:01:32 -0700 |
commit | afcdfff09f8557ce6810546628733f48d6c45408 (patch) | |
tree | 9c556c29a0556429dc1b421b2d5abade1915a034 /tools | |
parent | 46c82708d50e839945fa24094fe352241be6a22e (diff) | |
parent | cd38275faf739ba151c0aa7abe13703c9b8d8235 (diff) |
Merge remote-tracking branch 'origin/incoming' into touch_handling
Diffstat (limited to 'tools')
-rw-r--r-- | tools/asm_module.py | 4 | ||||
-rwxr-xr-x | tools/exec_llvm.py | 4 | ||||
-rw-r--r-- | tools/file_packager.py | 15 | ||||
-rw-r--r-- | tools/find_bigfuncs.py | 4 | ||||
-rw-r--r-- | tools/find_bigvars.py | 24 | ||||
-rw-r--r-- | tools/js-optimizer.js | 73 | ||||
-rwxr-xr-x | tools/nativize_llvm.py | 4 | ||||
-rw-r--r-- | tools/response_file.py | 4 | ||||
-rw-r--r-- | tools/settings_template_readonly.py | 2 | ||||
-rw-r--r-- | tools/shared.py | 212 | ||||
-rwxr-xr-x | tools/source-maps/sourcemapper.js | 43 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-outline1-output.js | 304 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-outline2-output.js | 576 |
13 files changed, 715 insertions, 554 deletions
diff --git a/tools/asm_module.py b/tools/asm_module.py index bf7fa71d..226b66b8 100644 --- a/tools/asm_module.py +++ b/tools/asm_module.py @@ -84,7 +84,9 @@ class AsmModule(): for table in self.tables: if table not in main.tables: sig = table[table.rfind('_')+1:] - all_sendings['invoke_%s' % sig] = shared.JS.make_invoke(sig, named=False) + 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 diff --git a/tools/exec_llvm.py b/tools/exec_llvm.py index 2685f9e4..ec7a8924 100755 --- a/tools/exec_llvm.py +++ b/tools/exec_llvm.py @@ -39,12 +39,12 @@ def path_from_root(*pathelems): sys.path += [path_from_root('')] from tools.shared import * -Popen([LLVM_OPT, sys.argv[1], '-strip-debug', '-o=' + sys.argv[1]+'.clean.bc']).communicate()[0] +Popen([LLVM_OPT, sys.argv[1], '-strip-debug', '-o', sys.argv[1]+'.clean.bc']).communicate()[0] # Execute with empty environment - just like the JS script will have Popen([LLVM_INTERPRETER, sys.argv[1]+'.clean.bc'] + sys.argv[2:], env={'HOME': '.'}).communicate()[0] -#Popen([LLVM_COMPILER, '-march=c', sys.argv[1], '-o=' + sys.argv[1]+'.cbe.c']).communicate()[0] +#Popen([LLVM_COMPILER, '-march=c', sys.argv[1], '-o', sys.argv[1]+'.cbe.c']).communicate()[0] #Popen(['gcc', sys.argv[1]+'.cbe.c', '-lstdc++']).communicate()[0] #Popen(['./a.out'] + sys.argv[2:]).communicate()[0] diff --git a/tools/file_packager.py b/tools/file_packager.py index 1a9d925b..33ccebad 100644 --- a/tools/file_packager.py +++ b/tools/file_packager.py @@ -40,7 +40,7 @@ TODO: You can also provide .crn files yourself, pre-crunched. With this o ''' import os, sys, shutil, random, uuid, ctypes - +import posixpath import shared from shared import Compression, execute, suffix, unsuffixed from subprocess import Popen, PIPE, STDOUT @@ -216,7 +216,8 @@ for file_ in data_files: file_['dstpath'] = file_['dstpath'].replace(os.path.sep, '/') # name in the filesystem, native and emulated if file_['dstpath'].endswith('/'): # If user has submitted a directory name as the destination but omitted the destination filename, use the filename from source file file_['dstpath'] = file_['dstpath'] + os.path.basename(file_['srcpath']) - if file_['dstpath'].startswith('./'): file_['dstpath'] = file_['dstpath'][2:] # remove redundant ./ prefix + # make destination path always relative to the root + file_['dstpath'] = posixpath.normpath(os.path.join('/', file_['dstpath'])) if DEBUG: print >> sys.stderr, 'Packaging file "' + file_['srcpath'] + '" to VFS in path "' + file_['dstpath'] + '".' @@ -341,6 +342,8 @@ if has_preloaded: counter = 0 for file_ in data_files: filename = file_['dstpath'] + dirname = os.path.dirname(filename) + basename = os.path.basename(filename) if file_['mode'] == 'embed': # Embed data = map(ord, open(file_['srcpath'], 'rb').read()) @@ -356,7 +359,7 @@ for file_ in data_files: str_data = str(chunk) else: str_data += '.concat(' + str(chunk) + ')' - code += '''Module['FS_createDataFile']('/%s', '%s', %s, true, true);\n''' % (os.path.dirname(filename), os.path.basename(filename), str_data) + code += '''Module['FS_createDataFile']('%s', '%s', %s, true, true);\n''' % (dirname, basename, str_data) elif file_['mode'] == 'preload': # Preload varname = 'filePreload%d' % counter @@ -389,7 +392,7 @@ for file_ in data_files: assert(arrayBuffer, 'Loading file %(filename)s failed.'); var byteArray = !arrayBuffer.subarray ? new Uint8Array(arrayBuffer) : arrayBuffer; %(prepare)s - Module['FS_createPreloadedFile']('/%(dirname)s', '%(basename)s', byteArray, true, true, function() { + Module['FS_createPreloadedFile']('%(dirname)s', '%(basename)s', byteArray, true, true, function() { %(finish)s }%(fail)s); }; @@ -399,8 +402,8 @@ for file_ in data_files: 'request': 'DataRequest', # In the past we also supported XHRs here 'varname': varname, 'filename': filename, - 'dirname': os.path.dirname(filename), - 'basename': os.path.basename(filename), + 'dirname': dirname, + 'basename': basename, 'prepare': prepare, 'finish': finish, 'fail': '' if filename[-4:] not in AUDIO_SUFFIXES else ''', function() { Module['removeRunDependency']('fp %s') }''' % filename # workaround for chromium bug 124926 (still no audio with this, but at least we don't hang) diff --git a/tools/find_bigfuncs.py b/tools/find_bigfuncs.py index 6fdec3a9..79136343 100644 --- a/tools/find_bigfuncs.py +++ b/tools/find_bigfuncs.py @@ -1,5 +1,5 @@ ''' -Simple tool to find big functions in an .ll file. +Simple tool to find big functions in a js or ll file ''' import os, sys, re @@ -11,7 +11,7 @@ curr = None data = [] for line in open(filename): i += 1 - if line.startswith('function '): + if line.startswith(('function ', 'define ')): start = i curr = line elif line.startswith('}') and curr: diff --git a/tools/find_bigvars.py b/tools/find_bigvars.py new file mode 100644 index 00000000..6bee5dd4 --- /dev/null +++ b/tools/find_bigvars.py @@ -0,0 +1,24 @@ +''' +Simple tool to find functions with lots of vars. +''' + +import os, sys, re + +filename = sys.argv[1] +i = 0 +curr = None +data = [] +size = 0 +for line in open(filename): + i += 1 + if line.startswith('function '): + size = len(line.split(',')) # params + curr = line + elif line.strip().startswith('var '): + size += len(line.split(',')) + 1 # vars + elif line.startswith('}') and curr: + data.append([curr, size]) + curr = None +data.sort(lambda x, y: x[1] - y[1]) +print ''.join(['%6d : %s' % (x[1], x[0]) for x in data]) + diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 4192ddd1..e61317af 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -801,7 +801,10 @@ function simplifyExpressions(ast) { // HEAP[x >> 2] // very often. We can in some cases do the shift on the variable itself when it is set, // to greatly reduce the number of shift operations. -// TODO: when shifting a variable, if there are other uses, keep an unshifted version too, to prevent slowdowns? +// XXX this optimization is deprecated and currently invalid: does not handle overflows +// or non-aligned (round numbers, x >> 2 is a multiple of 4). Both are ok to assume +// for pointers (undefined behavior otherwise), but invalid in general, and we do +// no sufficiently-well distinguish the cases. function optimizeShiftsInternal(ast, conservative) { var MAX_SHIFTS = 3; traverseGeneratedFunctions(ast, function(fun) { @@ -3001,7 +3004,7 @@ function outline(ast) { // Try to flatten out code as much as possible, to make outlining more feasible. function flatten(func, asmData) { - var minSize = sizeToOutline; + var minSize = extraInfo.sizeToOutline/4; var helperId = 0; function getHelper() { while (1) { @@ -3106,7 +3109,9 @@ function outline(ast) { // Reserve an extra two spots per possible outlining: one for control flow var, the other for control flow data // The control variables are zeroed out when calling an outlined function, and after using // the value after they return. - asmData.maxOutlinings = Math.round(3*measureSize(func)/extraInfo.sizeToOutline); + var size = measureSize(func); + asmData.maxOutlinings = Math.round(3*size/extraInfo.sizeToOutline); + asmData.intendedPieces = Math.ceil(size/extraInfo.sizeToOutline); asmData.totalStackSize = stackSize + (stack.length + 2*asmData.maxOutlinings)*8; asmData.controlStackPos = function(i) { return stackSize + (stack.length + i)*8 }; asmData.controlDataStackPos = function(i) { return stackSize + (stack.length + i)*8 + 4 }; @@ -3211,16 +3216,18 @@ function outline(ast) { var CONTROL_BREAK = 1, CONTROL_BREAK_LABEL = 2, CONTROL_CONTINUE = 3, CONTROL_CONTINUE_LABEL = 4, CONTROL_RETURN_VOID = 5, CONTROL_RETURN_INT = 6, CONTROL_RETURN_DOUBLE = 7; var sizeToOutline = null; // customized per function and as we make progress - function calculateThreshold(func) { - sizeToOutline = extraInfo.sizeToOutline; + function calculateThreshold(func, asmData) { var size = measureSize(func); - //var desiredChunks = Math.ceil(size/extraInfo.sizeToOutline); - ////sizeToOutline = Math.round((extraInfo.sizeToOutline + (2*size/desiredChunks))/3); - //sizeToOutline = Math.round(size/desiredChunks); - printErr('trying to reduce the size of ' + func[1] + ' which is ' + size + ' (>= ' + extraInfo.sizeToOutline + '), aim for ' + sizeToOutline); + if (size <= extraInfo.sizeToOutline) { + sizeToOutline = Infinity; + printErr(' no point in trying to reduce the size of ' + func[1] + ' which is ' + size + ' <= ' + extraInfo.sizeToOutline); + } else { + sizeToOutline = Math.round(size/Math.max(2, asmData.intendedPieces--)); + printErr('trying to reduce the size of ' + func[1] + ' which is ' + size + ' (>=? ' + extraInfo.sizeToOutline + '), aim for ' + sizeToOutline); + } } - var level = 0; + var level = 0, loops = 0; var outliningParents = {}; // function name => parent it was outlined from function doOutline(func, asmData, stats, start, end) { @@ -3241,21 +3248,29 @@ function outline(ast) { } }); var reps = []; - // wipe out control variable - reps.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', 0])]); - reps.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlDataStackPos(outlineIndex)), ['num', 0])]); // XXX not really needed - // add spills and reads before and after the call to the outlined code, and in the outlined code itself - keys(setUnion(codeInfo.reads, codeInfo.writes)).forEach(function(v) { + // add spills + function orderFunc(x, y) { + return (asmData.stackPos[x] - asmData.stackPos[y]) || x.localeCompare(y); + } + var sortedReadsAndWrites = keys(setUnion(codeInfo.reads, codeInfo.writes)).sort(orderFunc); + var sortedWrites = keys(codeInfo.writes).sort(orderFunc); + sortedReadsAndWrites.forEach(function(v) { if (!(v in owned)) { reps.push(['stat', ['assign', true, ['sub', ['name', getAsmType(v, asmData) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]], ['name', v]]]); } }); + // wipe out control variable + reps.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', 0])]); + reps.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlDataStackPos(outlineIndex)), ['num', 0])]); // XXX not really needed + // do the call reps.push(['stat', ['call', ['name', newIdent], [['name', 'sp']]]]); - for (var v in codeInfo.writes) { + // add unspills + sortedWrites.forEach(function(v) { if (!(v in owned)) { reps.push(['stat', ['assign', true, ['name', v], makeAsmCoercion(['sub', ['name', getAsmType(v, asmData) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]], getAsmType(v, asmData))]]); } - } + }); + // Generate new function if (codeInfo.hasReturn || codeInfo.hasReturnInt || codeInfo.hasReturnDouble || codeInfo.hasBreak || codeInfo.hasContinue) { // we need to capture all control flow using a top-level labeled one-time loop in the outlined function @@ -3389,16 +3404,17 @@ function outline(ast) { } } // add spills and unspills in outlined code outside the OL loop - keys(setUnion(codeInfo.reads, codeInfo.writes)).forEach(function(v) { + sortedReadsAndWrites.reverse(); + sortedReadsAndWrites.forEach(function(v) { if (!(v in owned)) { code.unshift(['stat', ['assign', true, ['name', v], makeAsmCoercion(['sub', ['name', getAsmType(v, asmData) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]], getAsmType(v, asmData))]]); } }); - for (var v in codeInfo.writes) { + sortedWrites.forEach(function(v) { if (!(v in owned)) { code.push(['stat', ['assign', true, ['sub', ['name', getAsmType(v, asmData) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]], ['name', v]]]); } - } + }); // finalize var newFunc = ['defun', newIdent, ['sp'], code]; var newAsmData = { params: { sp: ASM_INT }, vars: {} }; @@ -3436,8 +3452,8 @@ function outline(ast) { } } outliningParents[newIdent] = func[1]; - printErr('performed outline ' + [func[1], newIdent, 'code sizes (pre/post):', originalCodeSize, measureSize(code), 'overhead (w/r):', setSize(setSub(codeInfo.writes, owned)), setSize(setSub(codeInfo.reads, owned))]); - calculateThreshold(func); + printErr('performed outline ' + [func[1], newIdent, 'code sizes (pre/post):', originalCodeSize, measureSize(code), 'overhead (w/r):', setSize(setSub(codeInfo.writes, owned)), setSize(setSub(codeInfo.reads, owned)), ' owned: ', setSize(owned), ' left: ', setSize(asmData.vars), setSize(asmData.params), ' loopsDepth: ', loops]); + calculateThreshold(func, asmData); return [newFunc]; } @@ -3462,6 +3478,9 @@ function outline(ast) { } } } + function done() { + return asmData.splitCounter >= asmData.maxOutlinings || measureSize(func) <= extraInfo.sizeToOutline; + } while (1) { i--; calcMinIndex(); // TODO: optimize @@ -3506,11 +3525,18 @@ function outline(ast) { if (subRet && subRet.length > 0) ret.push.apply(ret, subRet); } return null; // do not recurse into children, outlineStatements will do so if necessary + } else if (type == 'while') { + loops++; + } + }, function(node, type) { + if (type == 'while') { + loops--; } }); if (ret.length > pre) { // we outlined recursively, reset our state here //printErr('successful outline in recursion ' + func[1] + ' due to recursive in level ' + level); + if (done()) break; end = i-1; sizeSeen = 0; canRestart = true; @@ -3550,6 +3576,7 @@ function outline(ast) { if (newFuncs.length) { ret.push.apply(ret, newFuncs); } + if (done()) break; sizeSeen = 0; end = i-1; canRestart = true; @@ -3581,8 +3608,8 @@ function outline(ast) { if (size >= extraInfo.sizeToOutline) { aggressiveVariableElimination(func, asmData); flatten(func, asmData); - calculateThreshold(func); analyzeFunction(func, asmData); + calculateThreshold(func, asmData); var stats = getStatements(func); var ret = outlineStatements(func, asmData, stats, 0.9*size); assert(level == 0); diff --git a/tools/nativize_llvm.py b/tools/nativize_llvm.py index d9558c32..413c8d14 100755 --- a/tools/nativize_llvm.py +++ b/tools/nativize_llvm.py @@ -21,11 +21,11 @@ filename = sys.argv[1] libs = sys.argv[2:] # e.g.: dl for dlopen/dlclose, util for openpty/forkpty print 'bc => clean bc' -Popen([LLVM_OPT, filename, '-strip-debug', '-o=' + filename + '.clean.bc']).communicate()[0] +Popen([LLVM_OPT, filename, '-strip-debug', '-o', filename + '.clean.bc']).communicate()[0] print 'bc => s' for params in [[], ['-march=x86-64']]: # try x86, then x86-64 FIXME print 'params', params - Popen([LLVM_COMPILER] + params + [filename + '.clean.bc', '-o=' + filename + '.s']).communicate()[0] + Popen([LLVM_COMPILER] + params + [filename + '.clean.bc', '-o', filename + '.s']).communicate()[0] print 's => o' Popen(['as', filename + '.s', '-o', filename + '.o']).communicate()[0] if os.path.exists(filename + '.o'): break diff --git a/tools/response_file.py b/tools/response_file.py index 312cda73..f19cf8af 100644 --- a/tools/response_file.py +++ b/tools/response_file.py @@ -6,8 +6,8 @@ def create_response_file(args, directory): (response_fd, response_filename) = tempfile.mkstemp(prefix='emscripten_', suffix='.rsp', dir=directory, text=True) response_fd = os.fdopen(response_fd, "w") #print >> sys.stderr, "Creating response file '%s'" % response_filename - args = map(lambda p: p.replace(' ', '').replace('\\', '\\\\').replace('"', '\\"'), args) - response_fd.write(' '.join(args)) + args = map(lambda p: p.replace('\\', '\\\\').replace('"', '\\"'), args) + response_fd.write('"' + '" "'.join(args) + '"') response_fd.close() return response_filename diff --git a/tools/settings_template_readonly.py b/tools/settings_template_readonly.py index 7ab89b48..14b45a20 100644 --- a/tools/settings_template_readonly.py +++ b/tools/settings_template_readonly.py @@ -16,7 +16,7 @@ V8_ENGINE = os.path.expanduser(os.getenv('V8') or 'd8') # executable JAVA = 'java' # executable -TEMP_DIR = '/tmp' # You will need to modify this on Windows +TEMP_DIR = '{{{ TEMP }}}' #CLOSURE_COMPILER = '..' # define this to not use the bundled version diff --git a/tools/shared.py b/tools/shared.py index c0df227d..0d0f20d4 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -200,18 +200,25 @@ if '\n' in EM_CONFIG: else: CONFIG_FILE = os.path.expanduser(EM_CONFIG) if not os.path.exists(CONFIG_FILE): + # Note: repr is used to ensure the paths are escaped correctly on Windows. + # The full string is replaced so that the template stays valid Python. config_file = open(path_from_root('tools', 'settings_template_readonly.py')).read().split('\n') config_file = config_file[1:] # remove "this file will be copied..." config_file = '\n'.join(config_file) # autodetect some default paths - config_file = config_file.replace('{{{ EMSCRIPTEN_ROOT }}}', __rootpath__) + config_file = config_file.replace('\'{{{ EMSCRIPTEN_ROOT }}}\'', repr(__rootpath__)) llvm_root = os.path.dirname(find_executable('llvm-dis') or '/usr/bin/llvm-dis') - config_file = config_file.replace('{{{ LLVM_ROOT }}}', llvm_root) + config_file = config_file.replace('\'{{{ LLVM_ROOT }}}\'', repr(llvm_root)) node = find_executable('node') or find_executable('nodejs') or 'node' - config_file = config_file.replace('{{{ NODE }}}', node) + config_file = config_file.replace('\'{{{ NODE }}}\'', repr(node)) python = find_executable('python2') or find_executable('python') or \ sys.executable or 'python' - config_file = config_file.replace('{{{ PYTHON }}}', python) + config_file = config_file.replace('\'{{{ PYTHON }}}\'', repr(python)) + if WINDOWS: + tempdir = os.environ.get('TEMP') or os.environ.get('TMP') or 'c:\\temp' + else: + tempdir = '/tmp' + config_file = config_file.replace('\'{{{ TEMP }}}\'', repr(tempdir)) # write open(CONFIG_FILE, 'w').write(config_file) @@ -283,7 +290,7 @@ def check_node_version(): # we re-check sanity when the settings are changed) # We also re-check sanity and clear the cache when the version changes -EMSCRIPTEN_VERSION = '1.5.3' +EMSCRIPTEN_VERSION = '1.5.5' def generate_sanity(): return EMSCRIPTEN_VERSION + '|' + get_llvm_target() + '|' + LLVM_ROOT @@ -333,7 +340,7 @@ def check_sanity(force=False): logging.critical('Node.js (%s) does not seem to work, check the paths in %s' % (NODE_JS, EM_CONFIG)) sys.exit(1) - for cmd in [CLANG, LLVM_LINK, LLVM_AR, LLVM_OPT, LLVM_AS, LLVM_DIS, LLVM_NM]: + for cmd in [CLANG, LINK_CMD[0], LLVM_AR, LLVM_OPT, LLVM_AS, LLVM_DIS, LLVM_NM]: if not os.path.exists(cmd) and not os.path.exists(cmd + '.exe'): # .exe extension required for Windows logging.critical('Cannot find %s, check the paths in %s' % (cmd, EM_CONFIG)) sys.exit(1) @@ -360,12 +367,31 @@ def check_sanity(force=False): # Tools/paths -LLVM_ADD_VERSION = os.getenv('LLVM_ADD_VERSION') -CLANG_ADD_VERSION = os.getenv('CLANG_ADD_VERSION') +try: + LLVM_ADD_VERSION +except NameError: + LLVM_ADD_VERSION = os.getenv('LLVM_ADD_VERSION') + +try: + CLANG_ADD_VERSION +except NameError: + CLANG_ADD_VERSION = os.getenv('CLANG_ADD_VERSION') + +USING_PNACL_TOOLCHAIN = os.path.exists(os.path.join(LLVM_ROOT, 'pnacl-clang')) + +def modify_prefix(tool): + if USING_PNACL_TOOLCHAIN: + if tool.startswith('llvm-'): + tool = tool[5:] + tool = 'pnacl-' + tool + if WINDOWS: + tool += '.bat' + return tool # Some distributions ship with multiple llvm versions so they add # the version to the binaries, cope with that def build_llvm_tool_path(tool): + tool = modify_prefix(tool) if LLVM_ADD_VERSION: return os.path.join(LLVM_ROOT, tool + "-" + LLVM_ADD_VERSION) else: @@ -374,6 +400,7 @@ def build_llvm_tool_path(tool): # Some distributions ship with multiple clang versions so they add # the version to the binaries, cope with that def build_clang_tool_path(tool): + tool = modify_prefix(tool) if CLANG_ADD_VERSION: return os.path.join(LLVM_ROOT, tool + "-" + CLANG_ADD_VERSION) else: @@ -382,7 +409,11 @@ def build_clang_tool_path(tool): CLANG_CC=os.path.expanduser(build_clang_tool_path('clang')) CLANG_CPP=os.path.expanduser(build_clang_tool_path('clang++')) CLANG=CLANG_CPP -LLVM_LINK=build_llvm_tool_path('llvm-link') +if USING_PNACL_TOOLCHAIN: + # The PNaCl toolchain doesn't have llvm-link, but we can fake it + LINK_CMD = [build_llvm_tool_path('llvm-ld'), '-nostdlib', '-r'] +else: + LINK_CMD = [build_llvm_tool_path('llvm-link')] LLVM_AR=build_llvm_tool_path('llvm-ar') LLVM_OPT=os.path.expanduser(build_llvm_tool_path('opt')) LLVM_AS=os.path.expanduser(build_llvm_tool_path('llvm-as')) @@ -519,6 +550,7 @@ if USE_EMSDK: # allows projects to override them) EMSDK_OPTS = ['-nostdinc', '-Xclang', '-nobuiltininc', '-Xclang', '-nostdsysteminc', '-Xclang', '-isystem' + path_from_root('system', 'local', 'include'), + '-Xclang', '-isystem' + path_from_root('system', 'include', 'compat'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'libcxx'), '-Xclang', '-isystem' + path_from_root('system', 'include'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'emscripten'), @@ -599,7 +631,7 @@ def line_splitter(data): return out -def limit_size(string, MAX=120*20): +def limit_size(string, MAX=12000*20): if len(string) < MAX: return string return string[0:MAX/2] + '\n[..]\n' + string[-MAX/2:] @@ -643,62 +675,89 @@ def expand_response(data): # Settings. A global singleton. Not pretty, but nicer than passing |, settings| everywhere -class Settings: - @classmethod - def reset(self): - class Settings2: - QUANTUM_SIZE = 4 - reset = Settings.reset - - # Given some emcc-type args (-O3, -s X=Y, etc.), fill Settings with the right settings - @classmethod - def load(self, args=[]): - # Load the JS defaults into python - settings = open(path_from_root('src', 'settings.js')).read().replace('var ', 'Settings.').replace('//', '#') - exec settings in globals() - - # Apply additional settings. First -O, then -s - for i in range(len(args)): - if args[i].startswith('-O'): - level = eval(args[i][2]) - Settings.apply_opt_level(level) - for i in range(len(args)): - if args[i] == '-s': - exec 'Settings.' + args[i+1] in globals() # execute the setting - - # Transforms the Settings information into emcc-compatible args (-s X=Y, etc.). Basically - # the reverse of load_settings, except for -Ox which is relevant there but not here - @classmethod - def serialize(self): - ret = [] - for key, value in Settings.__dict__.iteritems(): - if key == key.upper(): # this is a hack. all of our settings are ALL_CAPS, python internals are not - jsoned = json.dumps(value, sort_keys=True) - ret += ['-s', key + '=' + jsoned] - return ret - - @classmethod - def apply_opt_level(self, opt_level, noisy=False): - if opt_level >= 1: - Settings.ASM_JS = 1 - Settings.ASSERTIONS = 0 - Settings.DISABLE_EXCEPTION_CATCHING = 1 - Settings.EMIT_GENERATED_FUNCTIONS = 1 - if opt_level >= 2: - Settings.RELOOP = 1 - Settings.ALIASING_FUNCTION_POINTERS = 1 - if opt_level >= 3: - # Aside from these, -O3 also runs closure compiler and llvm lto - Settings.FORCE_ALIGNED_MEMORY = 1 - Settings.DOUBLE_MODE = 0 - Settings.PRECISE_I64_MATH = 0 - if noisy: logging.warning('Applying some potentially unsafe optimizations! (Use -O2 if this fails.)') - - global Settings - Settings = Settings2 - Settings.load() # load defaults - -Settings.reset() +class Settings2(type): + class __impl: + attrs = {} + + def __init__(self): + self.reset() + + @classmethod + def reset(self): + self.attrs = { 'QUANTUM_SIZE': 4 } + self.load() + + # Given some emcc-type args (-O3, -s X=Y, etc.), fill Settings with the right settings + @classmethod + def load(self, args=[]): + # Load the JS defaults into python + settings = open(path_from_root('src', 'settings.js')).read().replace('//', '#') + settings = re.sub(r'var ([\w\d]+)', r'self.attrs["\1"]', settings) + exec settings + + # Apply additional settings. First -O, then -s + for i in range(len(args)): + if args[i].startswith('-O'): + level = eval(args[i][2]) + self.apply_opt_level(level) + for i in range(len(args)): + if args[i] == '-s': + declare = re.sub(r'([\w\d]+)\s*=\s*(.+)', r'self.attrs["\1"]=\2;', args[i+1]) + exec declare + + # Transforms the Settings information into emcc-compatible args (-s X=Y, etc.). Basically + # the reverse of load_settings, except for -Ox which is relevant there but not here + @classmethod + def serialize(self): + ret = [] + for key, value in self.attrs.iteritems(): + if key == key.upper(): # this is a hack. all of our settings are ALL_CAPS, python internals are not + jsoned = json.dumps(value, sort_keys=True) + ret += ['-s', key + '=' + jsoned] + return ret + + @classmethod + def apply_opt_level(self, opt_level, noisy=False): + if opt_level >= 1: + self.attrs['ASM_JS'] = 1 + self.attrs['ASSERTIONS'] = 0 + self.attrs['DISABLE_EXCEPTION_CATCHING'] = 1 + self.attrs['EMIT_GENERATED_FUNCTIONS'] = 1 + if opt_level >= 2: + self.attrs['RELOOP'] = 1 + self.attrs['ALIASING_FUNCTION_POINTERS'] = 1 + if opt_level >= 3: + # Aside from these, -O3 also runs closure compiler and llvm lto + self.attrs['FORCE_ALIGNED_MEMORY'] = 1 + self.attrs['DOUBLE_MODE'] = 0 + self.attrs['PRECISE_I64_MATH'] = 0 + if noisy: logging.warning('Applying some potentially unsafe optimizations! (Use -O2 if this fails.)') + + def __getattr__(self, attr): + if attr in self.attrs: + return self.attrs[attr] + else: + raise AttributeError + + def __setattr__(self, attr, value): + self.attrs[attr] = value + + __instance = None + + @staticmethod + def instance(): + if Settings2.__instance is None: + Settings2.__instance = Settings2.__impl() + return Settings2.__instance + + def __getattr__(self, attr): + return getattr(self.instance(), attr) + + def __setattr__(self, attr, value): + return setattr(self.instance(), attr, value) + +class Settings(object): + __metaclass__ = Settings2 # Building @@ -706,6 +765,7 @@ class Building: COMPILER = CLANG LLVM_OPTS = False COMPILER_TEST_OPTS = [] # For use of the test runner + JS_ENGINE_OVERRIDE = None # Used to pass the JS engine override from runner.py -> test_benchmark.py @staticmethod def get_building_env(native=False): @@ -876,7 +936,10 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e @staticmethod 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) + # Tracking unresolveds is necessary for .a linking, see below. + # Specify all possible entry points to seed the linking process. + # For a simple application, this would just be "main". + unresolved_symbols = set([func[1:] for func in Settings.EXPORTED_FUNCTIONS]) resolved_symbols = set() temp_dirs = [] files = map(os.path.abspath, files) @@ -944,7 +1007,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e logging.debug('emcc: llvm-linking: %s to %s', actual_files, target) # check for too-long command line - link_cmd = [LLVM_LINK] + actual_files + ['-o', target] + link_cmd = LINK_CMD + actual_files + ['-o', target] # 8k is a bit of an arbitrary limit, but a reasonable one # for max command line size before we use a respose file response_file = None @@ -952,7 +1015,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e logging.debug('using response file for llvm-link') [response_fd, response_file] = mkstemp(suffix='.response', dir=TEMP_DIR) - link_cmd = [LLVM_LINK, "@" + response_file] + link_cmd = LINK_CMD + ["@" + response_file] response_fh = os.fdopen(response_fd, 'w') for arg in actual_files: @@ -996,7 +1059,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e opts = Building.pick_llvm_opts(opts) #opts += ['-debug-pass=Arguments'] logging.debug('emcc: LLVM opts: ' + str(opts)) - output = Popen([LLVM_OPT, filename] + opts + ['-o=' + filename + '.opt.bc'], stdout=PIPE).communicate()[0] + output = Popen([LLVM_OPT, filename] + opts + ['-o', filename + '.opt.bc'], stdout=PIPE).communicate()[0] assert os.path.exists(filename + '.opt.bc'), 'Failed to run llvm optimizations: ' + output shutil.move(filename + '.opt.bc', filename) @@ -1004,7 +1067,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e def llvm_opts(filename): # deprecated version, only for test runner. TODO: remove if Building.LLVM_OPTS: shutil.move(filename + '.o', filename + '.o.pre') - output = Popen([LLVM_OPT, filename + '.o.pre'] + Building.LLVM_OPT_OPTS + ['-o=' + filename + '.o'], stdout=PIPE).communicate()[0] + output = Popen([LLVM_OPT, filename + '.o.pre'] + Building.LLVM_OPT_OPTS + ['-o', filename + '.o'], stdout=PIPE).communicate()[0] assert os.path.exists(filename + '.o'), 'Failed to run llvm optimizations: ' + output @staticmethod @@ -1015,7 +1078,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e output_filename = input_filename + '.o.ll' input_filename = input_filename + '.o' try_delete(output_filename) - output = Popen([LLVM_DIS, input_filename, '-o=' + output_filename], stdout=PIPE).communicate()[0] + output = Popen([LLVM_DIS, input_filename, '-o', output_filename], stdout=PIPE).communicate()[0] assert os.path.exists(output_filename), 'Could not create .ll file: ' + output return output_filename @@ -1027,7 +1090,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e output_filename = input_filename + '.o' input_filename = input_filename + '.o.ll' try_delete(output_filename) - output = Popen([LLVM_AS, input_filename, '-o=' + output_filename], stdout=PIPE).communicate()[0] + output = Popen([LLVM_AS, input_filename, '-o', output_filename], stdout=PIPE).communicate()[0] assert os.path.exists(output_filename), 'Could not create bc file: ' + output return output_filename @@ -1048,6 +1111,9 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e for line in output.split('\n'): if len(line) == 0: continue parts = filter(lambda seg: len(seg) > 0, line.split(' ')) + # pnacl-nm will print zero offsets for bitcode + if len(parts) == 3 and parts[0] == "00000000": + parts.pop(0) if len(parts) == 2: # ignore lines with absolute offsets, these are not bitcode anyhow (e.g. |00000630 t d_source_name|) status, symbol = parts if status == 'U': diff --git a/tools/source-maps/sourcemapper.js b/tools/source-maps/sourcemapper.js index fa908900..06c9a227 100755 --- a/tools/source-maps/sourcemapper.js +++ b/tools/source-maps/sourcemapper.js @@ -16,6 +16,38 @@ function countLines(s) { return count; } +// For a minor optimization, only do win32->unix normalization if we are actually on Windows, +// which avoids redundantly scanning files if not needed. +var isWindows = (process.platform === 'win32'); + +var unixPathRe = new RegExp('\\\\', 'g'); +// Returns the given (possibly Windows) path p normalized to unix path separators '/'. +function toUnixPath(p) { + if (isWindows) { + return p.replace(unixPathRe, '/'); + } else { + return p; + } +} + +var unixLineEndRe = new RegExp('\r\n', 'g'); +// Returns the given (possibly Windows) text data t normalized to unix line endings '\n'. +function toUnixLineEnding(t) { + if (isWindows) { + return t.replace(unixLineEndRe, '\n'); + } else { + return t; + } +} + +// If path "p2" is a relative path, joins paths p1 and p2 to form "p1/p2". If p2 is an absolute path, "p2" is returned. +function joinPath(p1, p2) { + if (p2[0] == '/' || (p2.length >= 3 && p2[1] == ':' && (p2[2] == '/' || p2[2] == '\\'))) // Is p2 an absolute path? + return p2; + else + return toUnixPath(path.join(p1, p2)); +} + /* * Extracts the line (not block) comments from the generated function code and * invokes commentHandler with (comment content, line number of comment). This @@ -105,8 +137,7 @@ function generateMap(mappings, sourceRoot, mapFileBaseName, generatedLineOffset) // avoid doing it unnecessarily |