diff options
Diffstat (limited to 'emcc')
-rwxr-xr-x | emcc | 230 |
1 files changed, 177 insertions, 53 deletions
@@ -74,7 +74,7 @@ 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, tempfile, subprocess +import os, sys, shutil, tempfile, subprocess, shlex from subprocess import PIPE, STDOUT from tools import shared @@ -174,6 +174,12 @@ Options that are modified or new in %s include: 2: -O2 LLVM optimizations 3: -O3 LLVM optimizations (default in -O2+) + --llvm-lto <level> 0: No LLVM LTO (default in -O0) + 1: LLVM LTO (default in -O1+) + Note: If LLVM optimizations are not run + (see --llvm-opts), setting this to 1 has no + effect. + --closure <on> 0: No closure compiler (default in -O0, -O1) 1: Run closure compiler (default in -O2, -O3) @@ -232,6 +238,10 @@ Options that are modified or new in %s include: receive an array/typed array and return an array/typed array. Compression only works when generating HTML. + When compression is on, all filed specified + to be preloaded are compressed in one big + archive, which is given the same name as the + output HTML but with suffix .data.compress --minify <on> 0: Do not minify the generated JavaScript's whitespace (default if closure compiler @@ -307,7 +317,7 @@ if os.environ.get('EMMAKEN_CXX'): CC_ADDITIONAL_ARGS = shared.COMPILER_OPTS # + ['-g']? EMMAKEN_CFLAGS = os.environ.get('EMMAKEN_CFLAGS') -if EMMAKEN_CFLAGS: CC_ADDITIONAL_ARGS += EMMAKEN_CFLAGS.split(' ') +if EMMAKEN_CFLAGS: CC_ADDITIONAL_ARGS += shlex.split(EMMAKEN_CFLAGS) # ---------------- Utilities --------------- @@ -319,9 +329,11 @@ ASSEMBLY_SUFFIXES = ('.ll',) LIB_PREFIXES = ('', 'lib') IMAGE_SUFFIXES = ('.jpg', '.png', '.bmp') +AUDIO_SUFFIXES = ('.ogg', '.wav', '.mp3') +AUDIO_MIMETYPES = { 'ogg': 'audio/ogg', 'wav': 'audio/wav', 'mp3': 'audio/mpeg' } def suffix(name): - return name.split('.')[:-1] + return name.split('.')[-1] def unsuffixed(name): return '.'.join(name.split('.')[:-1]) @@ -347,6 +359,12 @@ for i in range(1, len(sys.argv)): if arg.endswith('.h') and sys.argv[i-1] != '-include': header = True +if '-M' in sys.argv or '-MM' in sys.argv: + # Just output dependencies, do not compile. Warning: clang and gcc behave differently with -MF! (clang seems to not recognize it) + cmd = [CC] + sys.argv[1:] + if DEBUG: print >> sys.stderr, 'emcc, just dependencies: ', ' '.join(cmd) + exit(subprocess.call(cmd)) + # Check if a target is specified target = None for i in range(len(sys.argv)-1): @@ -401,6 +419,7 @@ try: opt_level = 0 llvm_opts = None + llvm_lto = None closure = None js_transform = None pre_js = None @@ -427,6 +446,11 @@ try: llvm_opts = eval(newargs[i+1]) newargs[i] = '' newargs[i+1] = '' + elif newargs[i].startswith('--llvm-lto'): + check_bad_eq(newargs[i]) + llvm_lto = eval(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' elif newargs[i].startswith('--closure'): check_bad_eq(newargs[i]) closure = int(newargs[i+1]) @@ -474,12 +498,6 @@ try: Compression.on = True newargs[i] = '' newargs[i+1] = '' - elif newargs[i] == '-MF': # clang cannot handle this, so we fake it - f = open(newargs[i+1], 'w') - f.write('\n') - f.close() - newargs[i] = '' - newargs[i+1] = '' elif newargs[i] == '--ignore-dynamic-linking': ignore_dynamic_linking = True newargs[i] = '' @@ -491,6 +509,7 @@ try: newargs = [ arg for arg in newargs if arg is not '' ] if llvm_opts is None: llvm_opts = LLVM_OPT_LEVEL[opt_level] + if llvm_lto is None: llvm_lto = llvm_opts > 0 if closure is None: closure = 1 if opt_level >= 2 else 0 if minify_whitespace is None: minify_whitespace = closure # if closure is run, minify whitespace @@ -776,7 +795,7 @@ try: shared.Building.llvm_opt(in_temp(target_basename + '.bc'), llvm_opts) if DEBUG: save_intermediate('opt', 'bc') # Do LTO in a separate pass to work around LLVM bug XXX (see failure e.g. in cubescript) - if shared.Building.can_use_unsafe_opts() and shared.Building.can_build_standalone(): + if llvm_lto and shared.Building.can_use_unsafe_opts() and shared.Building.can_build_standalone(): lto_opts = [] if not shared.Building.can_inline(): lto_opts.append('-disable-inlining') lto_opts.append('-std-link-opts') @@ -813,6 +832,23 @@ try: if DEBUG: print >> sys.stderr, 'emcc: setting up files' code = '' + if final_suffix == 'html': + code += ''' + var BlobBuilder = typeof MozBlobBuilder != "undefined" ? MozBlobBuilder : (typeof WebKitBlobBuilder != "undefined" ? WebKitBlobBuilder : console.log("warning: cannot build blobs")); + var URLObject = typeof window != "undefined" ? (window.URL ? window.URL : window.webkitURL) : console.log("warning: cannot create object URLs"); + var hasBlobConstructor; + try { + new Blob(); + hasBlobConstructor = true; + } catch(e) { + hasBlobConstructor = false; + console.log("warning: no blob constructor, cannot create blobs with mimetypes"); + } +''' + + code += 'var preloadedImages = {}; // maps url to image data\n' + code += 'var preloadedAudios = {}; // maps url to audio data\n' + # Expand directories into individual files def add(mode, dirname, names): for name in names: @@ -827,19 +863,8 @@ try: for file_ in data_files: file_['net_name'] = file_['name'] - file_['compressed'] = False - if Compression.on: - # Compress each file, if it is worth it - for file_ in data_files: - Compression.compress(file_['name']) - if Compression.worth_it(os.stat(file_['name']).st_size, - os.stat(Compression.compressed_name(file_['name'])).st_size): - file_['net_name'] = Compression.compressed_name(file_['name']) - file_['compressed'] = True - else: - if DEBUG: print >> sys.stderr, 'emcc: not compressing %s since not worth it' % file_['name'] - os.remove(Compression.compressed_name(file_['name'])) + data_target = unsuffixed(target) + '.data' # Set up folders partial_dirs = [] @@ -853,8 +878,31 @@ try: code += '''FS.createFolder('/%s', '%s', true, false);\n''' % (os.path.sep.join(parts[:i]), parts[i]) partial_dirs.append(partial) - code += 'var BlobBuilder = typeof MozBlobBuilder != "undefined" ? MozBlobBuilder : (typeof WebKitBlobBuilder != "undefined" ? WebKitBlobBuilder : console.log("warning: cannot build blobs"));\n' - code += 'var URLObject = typeof window != "undefined" ? (window.URL ? window.URL : window.webkitURL) : console.log("warning: cannot create object URLs");\n' + if final_suffix == 'html': + # Bundle all datafiles into one archive. Avoids doing lots of simultaneous XHRs which has overhead. + data = open(data_target, 'wb') + start = 0 + for file_ in data_files: + file_['data_start'] = start + curr = open(file_['name']).read() + file_['data_end'] = start + len(curr) + start += len(curr) + data.write(curr) + data.close() + if Compression.on: + Compression.compress(data_target) + + # Data requests - for getting a block of data out of the big archive - have a similar API to XHRs + code += ''' + function DataRequest() {} + DataRequest.prototype = { + requests: {}, + open: function(mode, name) { + this.requests[name] = this; + }, + send: function() {} + }; + ''' counter = 0 for file_ in data_files: @@ -864,56 +912,121 @@ try: code += '''FS.createDataFile('/', '%s', %s, true, true);\n''' % (os.path.basename(filename), str(map(ord, open(filename, 'rb').read()))) elif file_['mode'] == 'preload': # Preload + assert final_suffix == 'html', 'Can only preload files when generating HTML' + varname = 'filePreload%d' % counter counter += 1 image = filename.endswith(IMAGE_SUFFIXES) + audio = filename.endswith(AUDIO_SUFFIXES) + + if image: + finish = ''' + var bb = new BlobBuilder(); + bb.append(byteArray.buffer); + var b = bb.getBlob(); + var url = URLObject.createObjectURL(b); + var img = new Image(); + img.onload = function() { + assert(img.complete, 'Image %(filename)s could not be decoded'); + var canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + preloadedImages['%(filename)s'] = canvas; + URLObject.revokeObjectURL(url); + removeRunDependency(); + }; + img.onerror = function(event) { + console.log('Image %(filename)s could not be decoded'); + }; + img.src = url; +''' % { 'filename': filename } + elif audio: + # Need actual blob constructor here, to set the mimetype or else audios fail to decode + finish = ''' + if (hasBlobConstructor) { + var b = new Blob([byteArray.buffer], { type: '%(mimetype)s' }); + var url = URLObject.createObjectURL(b); // XXX we never revoke this! + var audio = new Audio(); + audio['oncanplaythrough'] = function() { // XXX string for closure + audio['oncanplaythrough'] = null; + preloadedAudios['%(filename)s'] = audio; + removeRunDependency(); + }; + audio.onerror = function(event) { + console.log('Audio %(filename)s could not be decoded'); + }; + audio.src = url; + } else { + preloadedAudios['%(filename)s'] = new Audio(); // empty shim + removeRunDependency(); + } +''' % { 'filename': filename, 'mimetype': AUDIO_MIMETYPES[suffix(filename)] } + else: + finish = 'removeRunDependency();\n' + code += ''' - var %(varname)s = new XMLHttpRequest(); + var %(varname)s = new %(request)s(); %(varname)s.open('GET', '%(netname)s', true); %(varname)s.responseType = 'arraybuffer'; %(varname)s.onload = function() { - var arrayBuffer = %(varname)s.response; // Note: not X.responseText + var arrayBuffer = %(varname)s.response; assert(arrayBuffer, 'Loading file %(filename)s failed.'); - var byteArray = new Uint8Array(arrayBuffer); - %(decompress_start)s + var byteArray = arrayBuffer.byteLength ? new Uint8Array(arrayBuffer) : arrayBuffer; FS.createDataFile('/%(dirname)s', '%(basename)s', byteArray, true, true); %(finish)s - %(decompress_end)s }; addRunDependency(); %(varname)s.send(null); ''' % { + 'request': 'DataRequest', # In the past we also supported XHRs here 'varname': varname, 'filename': filename, 'netname': file_['net_name'], 'dirname': os.path.dirname(filename), 'basename': os.path.basename(filename), - 'decompress_start': '' if not file_['compressed'] else 'Module["decompress"](byteArray, function(decompressed) { byteArray = new Uint8Array(decompressed);', - 'decompress_end': '' if not file_['compressed'] else '});', - 'finish': 'removeRunDependency();' if not image else '''var bb = new BlobBuilder(); - bb.append(byteArray.buffer); - var b = bb.getBlob(); - var url = URLObject.createObjectURL(b); - var img = new Image(); - img.onload = function() { - assert(img.complete, 'Image %(filename)s could not be decoded'); - var canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - var ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0); - preloadedImages['%(filename)s'] = canvas; - URLObject.revokeObjectURL(url); - removeRunDependency(); - }; - img.onerror = function(event) { - console.log('Image %(filename)s could not be decoded: ' + JSON.stringify(event)); - }; - img.src = url; -''' % { 'filename': filename } + 'finish': finish } else: assert 0 + + if final_suffix == 'html': + # Get the big archive and split it up + use_data = '' + for file_ in data_files: + if file_['mode'] == 'preload': + use_data += ''' + curr = DataRequest.prototype.requests['%s']; + curr.response = byteArray.subarray(%d,%d); + curr.onload(); + ''' % (file_['name'], file_['data_start'], file_['data_end']) + use_data += ' removeRunDependency();\n' + + if Compression.on: + use_data = ''' + Module["decompress"](byteArray, function(decompressed) { + byteArray = new Uint8Array(decompressed); + %s + }); + ''' % use_data + + code += ''' + var dataFile = new XMLHttpRequest(); + dataFile.open('GET', '%s', true); + dataFile.responseType = 'arraybuffer'; + dataFile.onload = function() { + var arrayBuffer = dataFile.response; + assert(arrayBuffer, 'Loading data file failed.'); + var byteArray = new Uint8Array(arrayBuffer); + var curr; + %s + }; + addRunDependency(); + dataFile.send(null); + if (Module['setStatus']) Module['setStatus']('Downloading...'); + ''' % (Compression.compressed_name(data_target) if Compression.on else data_target, use_data) + src = open(final).read().replace('// {{PRE_RUN_ADDITIONS}}', code) final += '.files.js' open(final, 'w').write(src) @@ -932,7 +1045,7 @@ try: shutil.copyfile(final, final + '.tr.js') final += '.tr.js' if DEBUG: print >> sys.stderr, 'emcc: applying transform: %s' % js_transform - execute(js_transform.split(' ') + [os.path.abspath(final)]) + execute(shlex.split(js_transform) + [os.path.abspath(final)]) if DEBUG: save_intermediate('transformed') # It is useful to run several js optimizer passes together, to save on unneeded unparsing/reparsing @@ -1013,14 +1126,25 @@ try: decoding = ''' var decompressWorker = new Worker('decompress.js'); var decompressCallbacks = []; + var decompressions = 0; Module["decompress"] = function(data, callback) { var id = decompressCallbacks.length; decompressCallbacks.push(callback); decompressWorker.postMessage({ data: data, id: id }); + if (Module['setStatus']) { + decompressions++; + Module['setStatus']('Decompressing...'); + } }; decompressWorker.onmessage = function(event) { decompressCallbacks[event.data.id](event.data.data); decompressCallbacks[event.data.id] = null; + if (Module['setStatus']) { + decompressions--; + if (decompressions == 0) { + Module['setStatus'](''); + } + } }; var compiledCodeXHR = new XMLHttpRequest(); compiledCodeXHR.open('GET', '%s', true); |