diff options
50 files changed, 8037 insertions, 301 deletions
@@ -120,3 +120,4 @@ a license to everyone to use it as detailed in LICENSE.) * Jari Vetoniemi <mailroxas@gmail.com> * Sindre Sorhus <sindresorhus@gmail.com> * James S Urquhart <jamesu@gmail.com> +* Boris Gjenero <boris.gjenero@gmail.com> @@ -1206,8 +1206,10 @@ try: jcache = False fastcomp_opts = ['-pnacl-abi-simplify-preopt', '-pnacl-abi-simplify-postopt'] - if not shared.Settings.DISABLE_EXCEPTION_CATCHING: + if shared.Settings.DISABLE_EXCEPTION_CATCHING != 1: fastcomp_opts += ['-enable-emscripten-cxx-exceptions'] + if len(shared.Settings.EXCEPTION_CATCHING_WHITELIST) > 0: + fastcomp_opts += ['-emscripten-cxx-exceptions-whitelist=' + ','.join(shared.Settings.EXCEPTION_CATCHING_WHITELIST)] if shared.Settings.ASM_JS: assert opt_level >= 1 or fastcomp, 'asm.js requires -O1 or above' @@ -1960,27 +1962,36 @@ try: # It is useful to run several js optimizer passes together, to save on unneeded unparsing/reparsing js_optimizer_queue = [] js_optimizer_extra_info = {} + js_optimizer_queue_history = [] def flush_js_optimizer_queue(): - global final, js_optimizer_queue, js_optimizer_extra_info + global final, js_optimizer_queue, js_optimizer_extra_info, js_optimizer_queue_history if len(js_optimizer_extra_info) == 0: js_optimizer_extra_info = None if len(js_optimizer_queue) > 0 and not(not shared.Settings.ASM_JS and len(js_optimizer_queue) == 1 and js_optimizer_queue[0] == 'last'): - if DEBUG != '2': + + def add_opt_args(args): if shared.Settings.ASM_JS: - js_optimizer_queue = ['asm'] + js_optimizer_queue + args = ['asm'] + args + if shared.Settings.PRECISE_F32: + args = ['asmPreciseF32'] + args + return args + + if DEBUG != '2': + js_optimizer_queue = add_opt_args(js_optimizer_queue) logging.debug('applying js optimization passes: %s', js_optimizer_queue) final = shared.Building.js_optimizer(final, js_optimizer_queue, jcache, debug_level >= 4, js_optimizer_extra_info) js_transform_tempfiles.append(final) if DEBUG: save_intermediate('js_opts') else: for name in js_optimizer_queue: - passes = [name] + passes = add_opt_args([name]) if shared.Settings.ASM_JS: passes = ['asm'] + passes logging.debug('applying js optimization pass: %s', passes) final = shared.Building.js_optimizer(final, passes, jcache, debug_level >= 4, js_optimizer_extra_info) js_transform_tempfiles.append(final) save_intermediate(name) + js_optimizer_queue_history += js_optimizer_queue js_optimizer_queue = [] js_optimizer_extra_info = {} @@ -2066,15 +2077,78 @@ try: html.write(shell.replace('{{{ SCRIPT }}}', '<script>' + open(shared.path_from_root('src', 'proxyClient.js')).read().replace('{{{ filename }}}', target_basename) + '</script>')) shutil.move(final, js_target) elif not Compression.on: + # Normal code generation path if debug_level >= 4: generate_source_map(target) shutil.move(final, js_target) - # TODO: use an async blob, which would allow code rewriting on the client: - # var blob = new Blob([codeString]); - # var script = document.createElement('script'); - # script.src = URL.createObjectURL(blob); - # document.body.appendChild(script); - script_tag = '''<script async type="text/javascript" src="%s"></script>''' % base_js_target + need_mods = shared.Settings.PRECISE_F32 == 2 + if not need_mods: + # Non-modifiable code, just load the code directly + script_tag = '''<script async type="text/javascript" src="%s"></script>''' % base_js_target + else: + # Potentially-modifiable code, load as text, modify, then execute. This lets you + # patch the code on the client machine right before it is executed, perhaps based + # on information about the client. + checks = [] + mods = [] + if shared.Settings.PRECISE_F32 == 2: + checks.append('!Math.fround') + if 'minifyNames' not in js_optimizer_queue_history: + # simple dumb replace + mods.append(''' +console.log('optimizing out Math.fround calls'); +code = code.replace(/Math_fround\(/g, '(').replace("'use asm'", "'almost asm'") +''') + else: + # minified, not quite so simple - TODO + mods.append(''' +try { + console.log('optimizing out Math.fround calls'); + var m = /var ([^=]+)=global\.Math\.fround;/.exec(code); + var minified = m[1]; + if (!minified) throw 'fail'; + var startAsm = code.indexOf('// EMSCRIPTEN_START_FUNCS'); + var endAsm = code.indexOf('// EMSCRIPTEN_END_FUNCS'); + var asm = code.substring(startAsm, endAsm); + do { + var moar = false; // we need to re-do, as x(x( will not be fixed + asm = asm.replace(new RegExp('[^a-zA-Z0-9\\\\$\\\\_]' + minified + '\\\\(', 'g'), function(s) { moar = true; return s[0] + '(' }); + } while (moar); + code = code.substring(0, startAsm) + asm + code.substring(endAsm); + code = code.replace("'use asm'", "'almost asm'"); +} catch(e) { console.log('failed to optimize out Math.fround calls ' + e) } +''') + + fixes = '' + for i in range(len(checks)): + fixes += 'if (' + checks[i] + ') { ' + mods[i] + ' }\n' + + # if all the checks are negative, just emit a script tag normally, that's better. + # otherwise, do an xhr to get the code as text, modify, and load asynchronously + code = 'if (!(' + ' || '.join(checks) + ''')) { + var script = document.createElement('script'); + script.src = "''' + base_js_target + '''"; + document.body.appendChild(script); +} else { + var codeXHR = new XMLHttpRequest(); + codeXHR.open('GET', '%s', true); + codeXHR.onload = function() { + var code = codeXHR.responseText; + %s + var blob = new Blob([code], { type: 'text/javascript' }); + codeXHR = null; + var src = URL.createObjectURL(blob); + var script = document.createElement('script'); + script.src = URL.createObjectURL(blob); + script.onload = function() { + URL.revokeObjectURL(script.src); + }; + document.body.appendChild(script); + }; + codeXHR.send(null); +} +''' % (base_js_target, fixes) + script_tag = '''<script>%s</script>''' % code html.write(shell.replace('{{{ SCRIPT }}}', script_tag)) else: # Compress the main code diff --git a/emscripten.py b/emscripten.py index 77082aee..c96c56a0 100755 --- a/emscripten.py +++ b/emscripten.py @@ -737,14 +737,17 @@ def emscript_fast(infile, settings, outfile, libraries=[], compiler_engine=None, # * Run compiler.js on the metadata to emit the shell js code, pre/post-ambles, # JS library dependencies, etc. - if DEBUG: - logging.debug('emscript: llvm backend') - t = time.time() - temp_js = temp_files.get('.4.js').name backend_compiler = os.path.join(shared.LLVM_ROOT, 'llc') - shared.jsrun.timeout_run(subprocess.Popen([backend_compiler, infile, '-march=js', '-filetype=asm', '-o', temp_js], stdout=subprocess.PIPE)) - + backend_args = [backend_compiler, infile, '-march=js', '-filetype=asm', '-o', temp_js] + if settings['PRECISE_F32']: + backend_args += ['-emscripten-precise-f32'] + if settings['WARN_UNALIGNED']: + backend_args += ['-emscripten-warn-unaligned'] + if DEBUG: + logging.debug('emscript: llvm backend: ' + ' '.join(backend_args)) + t = time.time() + shared.jsrun.timeout_run(subprocess.Popen(backend_args, stdout=subprocess.PIPE)) if DEBUG: logging.debug(' emscript: llvm backend took %s seconds' % (time.time() - t)) t = time.time() @@ -862,15 +865,13 @@ def emscript_fast(infile, settings, outfile, libraries=[], compiler_engine=None, pre = parts[0] funcs_js.append(parts[1]) - # calculations on merged forwarded data TODO - # merge forwarded data assert settings.get('ASM_JS'), 'fastcomp is asm.js only' settings['EXPORTED_FUNCTIONS'] = forwarded_json['EXPORTED_FUNCTIONS'] all_exported_functions = set(settings['EXPORTED_FUNCTIONS']) # both asm.js and otherwise for additional_export in settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE']: # additional functions to export from asm, if they are implemented all_exported_functions.add('_' + additional_export) - exported_implemented_functions = set() + exported_implemented_functions = set(metadata['exports']) export_bindings = settings['EXPORT_BINDINGS'] export_all = settings['EXPORT_ALL'] for key in metadata['implementedFunctions'] + forwarded_json['Functions']['implementedFunctions'].keys(): # XXX perf @@ -908,12 +909,15 @@ def emscript_fast(infile, settings, outfile, libraries=[], compiler_engine=None, else: pre_tables = '' + def unfloat(s): + return 'd' if s == 'f' else s # lower float to double for ffis + def make_table(sig, raw): i = Counter.i Counter.i += 1 bad = 'b' + str(i) params = ','.join(['p%d' % p for p in range(len(sig)-1)]) - coerced_params = ','.join([shared.JS.make_coercion('p%d', sig[p+1], settings) % p for p in range(len(sig)-1)]) + coerced_params = ','.join([shared.JS.make_coercion('p%d', unfloat(sig[p+1]), settings) % p for p in range(len(sig)-1)]) coercions = ';'.join(['p%d = %s' % (p, shared.JS.make_coercion('p%d' % p, sig[p+1], settings)) for p in range(len(sig)-1)]) + ';' def make_func(name, code): return 'function %s(%s) { %s %s }' % (name, params, coercions, code) @@ -937,6 +941,8 @@ def emscript_fast(infile, settings, outfile, libraries=[], compiler_engine=None, if not call_ident.startswith('_') and not call_ident.startswith('Math_'): call_ident = '_' + call_ident code = call_ident + '(' + coerced_params + ')' if sig[0] != 'v': + # ffis cannot return float + if sig[0] == 'f': code = '+' + code code = 'return ' + shared.JS.make_coercion(code, sig[0], settings) code += ';' Counter.pre.append(make_func(item + '__wrapper', code)) diff --git a/src/headlessCanvas.js b/src/headlessCanvas.js index 6b0f9d47..4bd17a7b 100644 --- a/src/headlessCanvas.js +++ b/src/headlessCanvas.js @@ -446,6 +446,7 @@ function headlessCanvas() { case /* GL_MAX_FRAGMENT_UNIFORM_VECTORS */ 0x8DFD: return 4096; case /* GL_MAX_VARYING_VECTORS */ 0x8DFC: return 32; case /* GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS */ 0x8B4D: return 32; + case /* GL_ARRAY_BUFFER_BINDING */ 0x8894: return 0; default: console.log('getParameter ' + pname + '?'); return 0; } }, diff --git a/src/jsifier.js b/src/jsifier.js index ab5440f7..ab10f455 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -224,6 +224,7 @@ function JSify(data, functionsOnly) { // globalVariable function globalVariableHandler(item) { + function needsPostSet(value) { if (typeof value !== 'string') return false; // (' is ok, as it is something we can indexize later into a concrete int: ('{{ FI_ ... @@ -274,7 +275,9 @@ function JSify(data, functionsOnly) { constant = Runtime.alignMemory(calcAllocatedSize(item.type)); } else { if (item.external) { - if (Runtime.isNumberType(item.type) || isPointerType(item.type)) { + if (LibraryManager.library[item.ident.slice(1)]) { + constant = LibraryManager.library[item.ident.slice(1)]; + } else if (Runtime.isNumberType(item.type) || isPointerType(item.type)) { constant = zeros(Runtime.getNativeFieldSize(item.type)); } else { constant = makeEmptyStruct(item.type); @@ -282,22 +285,23 @@ function JSify(data, functionsOnly) { } else { constant = parseConst(item.value, item.type, item.ident); } - assert(typeof constant === 'object');//, [typeof constant, JSON.stringify(constant), item.external]); // This is a flattened object. We need to find its idents, so they can be assigned to later - var structTypes = null; - constant.forEach(function(value, i) { - if (needsPostSet(value)) { // ident, or expression containing an ident - if (!structTypes) structTypes = generateStructTypes(item.type); - itemsDict.GlobalVariablePostSet.push({ - intertype: 'GlobalVariablePostSet', - JS: makeSetValue(makeGlobalUse(item.ident), i, value, structTypes[i], false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors - }); - constant[i] = '0'; - } else { - if (typeof value === 'string') constant[i] = deParenCarefully(value); - } - }); + if (typeof constant === 'object') { + var structTypes = null; + constant.forEach(function(value, i) { + if (needsPostSet(value)) { // ident, or expression containing an ident + if (!structTypes) structTypes = generateStructTypes(item.type); + itemsDict.GlobalVariablePostSet.push({ + intertype: 'GlobalVariablePostSet', + JS: makeSetValue(makeGlobalUse(item.ident), i, value, structTypes[i], false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors + }); + constant[i] = '0'; + } else { + if (typeof value === 'string') constant[i] = deParenCarefully(value); + } + }); + } if (item.external) { // External variables in shared libraries should not be declared as @@ -312,14 +316,18 @@ function JSify(data, functionsOnly) { } // ensure alignment - var extra = Runtime.alignMemory(constant.length) - constant.length; - if (item.ident.substr(0, 5) == '__ZTV') extra += Runtime.alignMemory(QUANTUM_SIZE); - while (extra-- > 0) constant.push(0); + if (typeof constant === 'object') { + var extra = Runtime.alignMemory(constant.length) - constant.length; + if (item.ident.substr(0, 5) == '__ZTV') extra += Runtime.alignMemory(QUANTUM_SIZE); + while (extra-- > 0) constant.push(0); + } } // NOTE: This is the only place that could potentially create static // allocations in a shared library. - constant = makePointer(constant, null, allocator, item.type, index); + if (typeof constant === 'object') { + constant = makePointer(constant, null, allocator, item.type, index); + } var js = (index !== null ? '' : item.ident + '=') + constant; if (js) js += ';'; diff --git a/src/library.js b/src/library.js index 30453137..26ce8457 100644 --- a/src/library.js +++ b/src/library.js @@ -1641,6 +1641,7 @@ LibraryManager.library = { for (var i = 0; i < maxx; i++) { next = get(); {{{ makeSetValue('argPtr++', 0, 'next', 'i8') }}}; + if (next === 0) return i > 0 ? fields : fields-1; // we failed to read the full length of this field } formatIndex += nextC - formatIndex + 1; continue; @@ -3457,7 +3458,7 @@ LibraryManager.library = { rand_r: function(seedp) { seedp = seedp|0; var val = 0; - val = (Math_imul({{{ makeGetValueAsm('seedp', 0, 'i32') }}}, 31010991)|0) + 0x676e6177 | 0; + val = ((Math_imul({{{ makeGetValueAsm('seedp', 0, 'i32') }}}, 31010991)|0) + 0x676e6177 ) & {{{ cDefine('RAND_MAX') }}}; // assumes RAND_MAX is in bit mask form (power of 2 minus 1) {{{ makeSetValueAsm('seedp', 0, 'val', 'i32') }}}; return val|0; }, diff --git a/src/library_fs.js b/src/library_fs.js index e6b060f6..c30876f5 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -40,7 +40,17 @@ mergeInto(LibraryManager.library, { // lookupPath: function(path, opts) { path = PATH.resolve(FS.cwd(), path); - opts = opts || { recurse_count: 0 }; + opts = opts || {}; + + var defaults = { + follow_mount: true, + recurse_count: 0 + }; + for (var key in defaults) { + if (opts[key] === undefined) { + opts[key] = defaults[key]; + } + } if (opts.recurse_count > 8) { // max recursive lookup of 8 throw new FS.ErrnoError(ERRNO_CODES.ELOOP); @@ -67,10 +77,11 @@ mergeInto(LibraryManager.library, { // jump to the mount's root node if this is a mountpoint if (FS.isMountpoint(current)) { - current = current.mount.root; + if (!islast || (islast && opts.follow_mount)) { + current = current.mounted.root; + } } - // follow symlinks // by default, lookupPath will not follow a symlink if it is the final path component. // setting opts.follow = true will override this behavior. if (!islast || opts.follow) { @@ -163,28 +174,26 @@ mergeInto(LibraryManager.library, { createNode: function(parent, name, mode, rdev) { if (!FS.FSNode) { FS.FSNode = function(parent, name, mode, rdev) { + if (!parent) { + parent = this; // root node sets parent to itself + } + this.parent = parent; + this.mount = parent.mount; + this.mounted = null; this.id = FS.nextInode++; this.name = name; this.mode = mode; this.node_ops = {}; this.stream_ops = {}; this.rdev = rdev; - this.parent = null; - this.mount = null; - if (!parent) { - parent = this; // root node sets parent to itself - } - this.parent = parent; - this.mount = parent.mount; - FS.hashAddNode(this); }; + FS.FSNode.prototype = {}; + // compatibility var readMode = {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}}; var writeMode = {{{ cDefine('S_IWUGO') }}}; - FS.FSNode.prototype = {}; - // NOTE we must use Object.defineProperties instead of individual calls to // Object.defineProperty in order to make closure compiler happy Object.defineProperties(FS.FSNode.prototype, { @@ -204,7 +213,12 @@ mergeInto(LibraryManager.library, { }, }); } - return new FS.FSNode(parent, name, mode, rdev); + + var node = new FS.FSNode(parent, name, mode, rdev); + + FS.hashAddNode(node); + + return node; }, destroyNode: function(node) { FS.hashRemoveNode(node); @@ -213,7 +227,7 @@ mergeInto(LibraryManager.library, { return node === node.parent; }, isMountpoint: function(node) { - return node.mounted; + return !!node.mounted; }, isFile: function(mode) { return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFREG') }}}; @@ -441,61 +455,131 @@ mergeInto(LibraryManager.library, { // // core // + getMounts: function(mount) { + var mounts = []; + var check = [mount]; + + while (check.length) { + var m = check.pop(); + + mounts.push(m); + + check.push.apply(check, m.mounts); + } + + return mounts; + }, syncfs: function(populate, callback) { if (typeof(populate) === 'function') { callback = populate; populate = false; } + var mounts = FS.getMounts(FS.root.mount); var completed = 0; - var total = FS.mounts.length; + function done(err) { if (err) { - return callback(err); + if (!done.errored) { + done.errored = true; + return callback(err); + } + return; } - if (++completed >= total) { + if (++completed >= mounts.length) { callback(null); } }; // sync all mounts - for (var i = 0; i < FS.mounts.length; i++) { - var mount = FS.mounts[i]; + mounts.forEach(function (mount) { if (!mount.type.syncfs) { - done(null); - continue; + return done(null); } mount.type.syncfs(mount, populate, done); - } + }); }, mount: function(type, opts, mountpoint) { - var lookup; - if (mountpoint) { - lookup = FS.lookupPath(mountpoint, { follow: false }); + var root = mountpoint === '/'; + var pseudo = !mountpoint; + var node; + + if (root && FS.root) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } else if (!root && !pseudo) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + mountpoint = lookup.path; // use the absolute path + node = lookup.node; + + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + + if (!FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } } + var mount = { type: type, opts: opts, mountpoint: mountpoint, - root: null + mounts: [] }; + // create a root node for the fs - var root = type.mount(mount); - root.mount = mount; - mount.root = root; - // assign the mount info to the mountpoint's node - if (lookup) { - lookup.node.mount = mount; - lookup.node.mounted = true; - // compatibility update FS.root if we mount to / - if (mountpoint === '/') { - FS.root = mount.root; + var mountRoot = type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + + if (root) { + FS.root = mountRoot; + } else if (node) { + // set as a mountpoint + node.mounted = mount; + + // add the new mount to the current mount's children + if (node.mount) { + node.mount.mounts.push(mount); } } - // add to our cached list of mounts - FS.mounts.push(mount); - return root; + + return mountRoot; + }, + unmount: function (mountpoint) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + + if (!FS.isMountpoint(lookup.node)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + + // destroy the nodes for this mount, and all its child mounts + var node = lookup.node; + var mount = node.mounted; + var m |