diff options
-rwxr-xr-x | emcc | 29 | ||||
-rwxr-xr-x | emscripten.py | 2 | ||||
-rw-r--r-- | src/analyzer.js | 97 | ||||
-rw-r--r-- | src/compiler.js | 11 | ||||
-rw-r--r-- | src/intertyper.js | 25 | ||||
-rw-r--r-- | src/jsifier.js | 2 | ||||
-rw-r--r-- | src/library_browser.js | 1 | ||||
-rw-r--r-- | src/modules.js | 6 | ||||
-rw-r--r-- | src/parseTools.js | 14 | ||||
-rw-r--r-- | src/postamble.js | 1 | ||||
-rw-r--r-- | tests/test_core.py | 23 | ||||
-rw-r--r-- | tests/test_other.py | 20 | ||||
-rw-r--r-- | tests/test_sanity.py | 8 | ||||
-rw-r--r-- | tools/js-optimizer.js | 26 | ||||
-rw-r--r-- | tools/shared.py | 9 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-minlast-output.js | 2 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-minlast.js | 8 |
17 files changed, 191 insertions, 93 deletions
@@ -129,18 +129,18 @@ Most normal gcc/g++ options will work, for example: Options that are modified or new in %s include: -O0 No optimizations (default) -O1 Simple optimizations, including asm.js, LLVM -O1 - optimizations, and no runtime assertions + optimizations, relooping, and no runtime assertions or C++ exception catching (to re-enable C++ exception catching, use - -s DISABLE_EXCEPTION_CATCHING=0 ). - (For details on the affects of different - opt levels, see apply_opt_level() in - tools/shared.py and also src/settings.js.) - -O2 As -O1, plus the relooper (loop recreation), - LLVM -O3 optimizations, and + -s DISABLE_EXCEPTION_CATCHING=0 ), and enables -s ALIASING_FUNCTION_POINTERS=1 + (For details on the affects of different + opt levels, see apply_opt_level() in + tools/shared.py and also src/settings.js.) + -O2 As -O1, plus various js-level optimizations and + LLVM -O3 optimizations -O3 As -O2, plus dangerous optimizations that may break the generated code! This adds @@ -258,6 +258,9 @@ Options that are modified or new in %s include: try adjusting JAVA_HEAP_SIZE in the environment (for example, to 4096m for 4GB). + Note: Closure is only run if js opts are being + done (-O2 or above, or --js-opts 1). + --js-transform <cmd> <cmd> will be called on the generated code before it is optimized. This lets you modify the JavaScript, for example adding some code @@ -933,7 +936,7 @@ try: if default_cxx_std: newargs = newargs + [default_cxx_std] - if js_opts is None: js_opts = opt_level >= 1 + if js_opts is None: js_opts = opt_level >= 2 if llvm_opts is None: llvm_opts = LLVM_OPT_LEVEL[opt_level] if llvm_lto is None and opt_level >= 3: llvm_lto = 3 if opt_level == 0: debug_level = 4 @@ -1105,6 +1108,16 @@ try: shared.Settings.CORRECT_OVERFLOWS = 1 assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' + heap = 4096 + while heap < shared.Settings.TOTAL_MEMORY: + if heap <= 16*1024*1024: + heap *= 2 + else: + heap += 16*1024*1024 + if heap != shared.Settings.TOTAL_MEMORY: + logging.warning('increasing TOTAL_MEMORY to %d to be more reasonable for asm.js' % heap) + shared.Settings.TOTAL_MEMORY = heap + if shared.Settings.CORRECT_SIGNS >= 2 or shared.Settings.CORRECT_OVERFLOWS >= 2 or shared.Settings.CORRECT_ROUNDINGS >= 2: debug_level = 4 # must keep debug info to do line-by-line operations diff --git a/emscripten.py b/emscripten.py index 2e90fa48..2f55825c 100755 --- a/emscripten.py +++ b/emscripten.py @@ -346,7 +346,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, else: curr = i i += settings['FUNCTION_POINTER_ALIGNMENT'] - #logging.debug('function indexing', indexed, curr, sig) + #logging.debug('function indexing ' + str([indexed, curr, sig])) forwarded_json['Functions']['indexedFunctions'][indexed] = curr # make sure not to modify this python object later - we use it in indexize def split_32(x): diff --git a/src/analyzer.js b/src/analyzer.js index 17ad26ad..23bc81b4 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -802,27 +802,65 @@ function analyzer(data, sidePass) { var whole = shifts >= 0 ? Math.floor(shifts/32) : Math.ceil(shifts/32); var fraction = Math.abs(shifts % 32); if (signed) { - var signedFill = '(' + makeSignOp(sourceElements[sourceElements.length-1].ident, 'i' + sourceElements[sourceElements.length-1].bits, 're', 1, 1) + ' < 0 ? -1 : 0)'; - var signedKeepAlive = { intertype: 'value', ident: sourceElements[sourceElements.length-1].ident, type: 'i32' }; - } - for (var j = 0; j < targetElements.length; j++) { - var result = { - intertype: 'value', - ident: (j + whole >= 0 && j + whole < sourceElements.length) ? sourceElements[j + whole].ident : (signed ? signedFill : '0'), - params: [(signed && j + whole > sourceElements.length) ? signedKeepAlive : null], + var signedFill = { + intertype: 'mathop', + op: 'select', + variant: 's', type: 'i32', + params: [{ + intertype: 'mathop', + op: 'icmp', + variant: 'slt', + type: 'i32', + params: [ + { intertype: 'value', ident: sourceElements[sourceElements.length-1].ident, type: 'i' + Math.min(sourceBits, 32) }, + { intertype: 'value', ident: '0', type: 'i32' } + ] + }, + { intertype: 'value', ident: '-1', type: 'i32' }, + { intertype: 'value', ident: '0', type: 'i32' }, + ] }; - if (j == 0 && sourceBits < 32) { - // zext sign correction - result.ident = makeSignOp(result.ident, 'i' + sourceBits, isUnsignedOp(value.op) ? 'un' : 're', 1, 1); - } - if (fraction != 0) { - var other = { + } + for (var j = 0; j < targetElements.length; j++) { + var inBounds = j + whole >= 0 && j + whole < sourceElements.length; + var result; + if (inBounds || !signed) { + result = { intertype: 'value', - ident: (j + sign + whole >= 0 && j + sign + whole < sourceElements.length) ? sourceElements[j + sign + whole].ident : (signed ? signedFill : '0'), - params: [(signed && j + sign + whole > sourceElements.length) ? signedKeepAlive : null], - type: 'i32', + ident: inBounds ? sourceElements[j + whole].ident : '0', + type: 'i' + Math.min(sourceBits, 32), }; + if (j == 0 && sourceBits < 32) { + // zext sign correction + var result2 = { + intertype: 'mathop', + op: isUnsignedOp(value.op) ? 'zext' : 'sext', + params: [result, { + intertype: 'type', + ident: 'i32', + type: 'i' + sourceBits + }], + type: 'i32' + }; + result = result2; + } + } else { + // out of bounds and signed + result = copy(signedFill); + } + if (fraction != 0) { + var other; + var otherInBounds = j + sign + whole >= 0 && j + sign + whole < sourceElements.length; + if (otherInBounds || !signed) { + other = { + intertype: 'value', + ident: otherInBounds ? sourceElements[j + sign + whole].ident : '0', + type: 'i32', + }; + } else { + other = copy(signedFill); + } other = { intertype: 'mathop', op: shiftOp, @@ -872,10 +910,17 @@ function analyzer(data, sidePass) { } if (targetBits <= 32) { // We are generating a normal legal type here - legalValue = { - intertype: 'value', - ident: targetElements[0].ident + (targetBits < 32 ? '&' + (Math.pow(2, targetBits)-1) : ''), - type: 'rawJS' + legalValue = { intertype: 'value', ident: targetElements[0].ident, type: 'i32' }; + if (targetBits < 32) { + legalValue = { + intertype: 'mathop', + op: 'and', + type: 'i32', + params: [ + legalValue, + { intertype: 'value', ident: (Math.pow(2, targetBits)-1).toString(), type: 'i32' } + ] + } }; legalValue.assignTo = item.assignTo; toAdd.push(legalValue); @@ -1117,7 +1162,7 @@ function analyzer(data, sidePass) { rawLinesIndex: i }; if (variable.origin === 'alloca') { - variable.allocatedNum = item.allocatedNum; + variable.allocatedNum = item.ident; } if (variable.origin === 'call') { variable.type = getReturnType(variable.type); @@ -1608,9 +1653,9 @@ function analyzer(data, sidePass) { var lines = func.labels[0].lines; for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; + if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.ident)) break; item.allocatedSize = func.variables[item.assignTo].impl === VAR_EMULATED ? - calcAllocatedSize(item.allocatedType)*item.allocatedNum: 0; + calcAllocatedSize(item.allocatedType)*item.ident: 0; if (USE_TYPED_ARRAYS === 2) { // We need to keep the stack aligned item.allocatedSize = Runtime.forceAlign(item.allocatedSize, Runtime.STACK_ALIGN); @@ -1619,7 +1664,7 @@ function analyzer(data, sidePass) { var index = 0; for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; + if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.ident)) break; item.allocatedIndex = index; index += item.allocatedSize; delete item.allocatedSize; @@ -1647,7 +1692,7 @@ function analyzer(data, sidePass) { for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!finishedInitial && (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum))) { + if (!finishedInitial && (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.ident))) { finishedInitial = true; } if (item.intertype == 'alloca' && finishedInitial) { diff --git a/src/compiler.js b/src/compiler.js index 90060837..d490f454 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -185,7 +185,6 @@ if (SAFE_HEAP) USE_BSS = 0; // must initialize heap for safe heap assert(!(USE_TYPED_ARRAYS === 2 && QUANTUM_SIZE !== 4), 'For USE_TYPED_ARRAYS == 2, must have normal QUANTUM_SIZE of 4'); if (ASM_JS) { assert(!ALLOW_MEMORY_GROWTH, 'Cannot grow asm.js heap'); - assert((TOTAL_MEMORY&(TOTAL_MEMORY-1)) == 0, 'asm.js heap must be power of 2'); } assert(!(!NAMED_GLOBALS && BUILD_AS_SHARED_LIB), 'shared libraries must have named globals'); @@ -207,7 +206,12 @@ if (phase == 'pre') { if (VERBOSE) printErr('VERBOSE is on, this generates a lot of output and can slow down compilation'); // Load struct and define information. -var temp = JSON.parse(read(STRUCT_INFO)); +try { + var temp = JSON.parse(read(STRUCT_INFO)); +} catch(e) { + printErr('cannot load struct info at ' + STRUCT_INFO + ' : ' + e + ', trying in current dir'); + temp = JSON.parse(read('struct_info.compiled.json')); +} C_STRUCTS = temp.structs; C_DEFINES = temp.defines; @@ -312,3 +316,6 @@ if (ll_file) { } } +//var M = keys(tokenCacheMisses).map(function(m) { return [m, misses[m]] }).sort(function(a, b) { return a[1] - b[1] }); +//printErr(dump(M.slice(M.length-10))); + diff --git a/src/intertyper.js b/src/intertyper.js index 09bdaa33..ac9a365f 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -5,6 +5,11 @@ var fastPaths = 0, slowPaths = 0; +var tokenCache = {}; +['=', 'i32', 'label', ';', '4', '0', '1', '2', '255', 'align', 'i8*', 'i8', 'i16', 'getelementptr', 'inbounds', 'unnamed_addr', 'x', 'load', 'preds', 'br', 'i32*', 'i1', 'store', '<label>', 'constant', 'c', 'private', 'null', 'internal', 'to', 'bitcast', 'define', 'nounwind', 'nocapture', '%this', 'call', '...'].forEach(function(text) { tokenCache[text] = { text: text } }); + +//var tokenCacheMisses = {}; + // Line tokenizer function tokenizer(item, inner) { //assert(item.lineNum != 40000); @@ -12,7 +17,6 @@ function tokenizer(item, inner) { var tokens = []; var quotes = 0; var lastToken = null; - var CHUNKSIZE = 64; // How much forward to peek forward. Too much means too many string segments copied // Note: '{' is not an encloser, as its use in functions is split over many lines var enclosers = { '[': 0, @@ -31,6 +35,15 @@ function tokenizer(item, inner) { return; } + var cached = tokenCache[text]; + if (cached) { + //assert(cached.text === text); + tokens.push(cached); + lastToken = cached; + return; + } + //tokenCacheMisses[text] = (misses[text] || 0) + 1; + var token = { text: text }; @@ -42,6 +55,10 @@ function tokenizer(item, inner) { } // merge certain tokens if (lastToken && isType(lastToken.text) && isFunctionDef(token)) { + if (lastToken.text in tokenCache) { + // create a copy of the cached value + lastToken = tokens[tokens.length-1] = { text: lastToken.text }; + } lastToken.text += ' ' + text; } else if (lastToken && text[0] == '}') { // }, }*, etc. var openBrace = tokens.length-1; @@ -766,15 +783,13 @@ function intertyper(lines, sidePass, baseLineNums) { return item; } // 'alloca' - var allocaPossibleVars = ['allocatedNum']; function allocaHandler(item) { item.intertype = 'alloca'; item.allocatedType = item.tokens[1].text; if (item.tokens.length > 3 && Runtime.isNumberType(item.tokens[3].text)) { - item.allocatedNum = toNiceIdent(item.tokens[4].text); - item.possibleVars = allocaPossibleVars; + item.ident = toNiceIdent(item.tokens[4].text); } else { - item.allocatedNum = 1; + item.ident = 1; } item.type = addPointing(item.tokens[1].text); // type of pointer we will get Types.needAnalysis[item.type] = 0; diff --git a/src/jsifier.js b/src/jsifier.js index 0e5f8ef3..e7cc3683 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1361,7 +1361,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (item.allocatedSize === 0) return ''; // This will not actually be shown - it's nativized return asmCoercion(getFastValue('sp', '+', item.allocatedIndex.toString()), 'i32'); } else { - return RuntimeGenerator.stackAlloc(getFastValue(calcAllocatedSize(item.allocatedType), '*', item.allocatedNum)); + return RuntimeGenerator.stackAlloc(getFastValue(calcAllocatedSize(item.allocatedType), '*', item.ident)); } } function va_argHandler(item) { diff --git a/src/library_browser.js b/src/library_browser.js index cba8ecdf..dd60a581 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -749,6 +749,7 @@ mergeInto(LibraryManager.library, { if (e instanceof ExitStatus) { return; } else { + if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]); throw e; } } diff --git a/src/modules.js b/src/modules.js index cc9ca549..76e5db11 100644 --- a/src/modules.js +++ b/src/modules.js @@ -285,11 +285,7 @@ var Functions = { } else { if (!singlePhase) return 'NO_INDEX'; // Should not index functions in post ret = this.indexedFunctions[ident]; - if (!ret) { - ret = this.nextIndex; - this.nextIndex += FUNCTION_POINTER_ALIGNMENT; - this.indexedFunctions[ident] = ret; - } + assert(ret); ret = ret.toString(); } if (SIDE_MODULE && sig) { // sig can be undefined for the GL library functions diff --git a/src/parseTools.js b/src/parseTools.js index addf0f21..1f875584 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -914,13 +914,13 @@ function parseI64Constant(str, legalized) { } function parseNumerical(value, type) { - if ((!type || type == 'double' || type == 'float') && (value.substr && value.substr(0,2) == '0x')) { + if ((!type || type === 'double' || type === 'float') && /^0x/.test(value)) { // Hexadecimal double value, as the llvm docs say, // "The one non-intuitive notation for constants is the hexadecimal form of floating point constants." value = IEEEUnHex(value); } else if (USE_TYPED_ARRAYS == 2 && isIllegalType(type)) { return value; // do not parseFloat etc., that can lead to loss of precision - } else if (value == 'null') { + } else if (value === 'null') { // NULL *is* 0, in C/C++. No JS null! (null == 0 is false, etc.) value = '0'; } else if (value === 'true') { @@ -930,7 +930,7 @@ function parseNumerical(value, type) { } if (isNumber(value)) { var ret = parseFloat(value); // will change e.g. 5.000000e+01 to 50 - if (type in Runtime.FLOAT_TYPES) { + if (type === 'double' || type === 'float') { if (value[0] === '-' && ret === 0) return '-.0'; // fix negative 0, toString makes it 0 if (!RUNNING_JS_OPTS) ret = asmEnsureFloat(ret, type); } @@ -2474,13 +2474,6 @@ function walkInterdata(item, pre, post, obj) { if (walkInterdata(item.params[i], pre, post, obj)) return true; } } - if (item.possibleVars) { // other attributes that might contain interesting data; here, variables - var box = { intertype: 'value', ident: '' }; - for (i = 0; i <= item.possibleVars.length; i++) { - box.ident = item[item.possibleVars[i]]; - if (walkInterdata(box, pre, post, obj)) return true; - } - } return post && post(item, originalObj, obj); } @@ -2500,7 +2493,6 @@ function walkAndModifyInterdata(item, pre) { if (repl = walkAndModifyInterdata(item.params[i], pre)) item.params[i] = repl; } } - // Ignore possibleVars because we can't replace them anyhow } function parseBlockAddress(segment) { diff --git a/src/postamble.js b/src/postamble.js index cd892733..62edeeb6 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -96,6 +96,7 @@ Module['callMain'] = Module.callMain = function callMain(args) { Module['noExitRuntime'] = true; return; } else { + if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]); throw e; } } finally { diff --git a/tests/test_core.py b/tests/test_core.py index c1bfce6f..576cb8c7 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1073,7 +1073,6 @@ Succeeded! self.do_run(open(path_from_root('tests', 'cube2md5.cpp')).read(), open(path_from_root('tests', 'cube2md5.ok')).read()) def test_cube2hash(self): - try: old_chunk_size = os.environ.get('EMSCRIPT_MAX_CHUNK_SIZE') or '' os.environ['EMSCRIPT_MAX_CHUNK_SIZE'] = '1' # test splitting out each function to a chunk in emscripten.py (21 functions here) @@ -1091,6 +1090,17 @@ Succeeded! finally: os.environ['EMSCRIPT_MAX_CHUNK_SIZE'] = old_chunk_size + assert 'asm1' in test_modes + if self.run_name == 'asm1': + assert Settings.RELOOP + generated = open('src.cpp.o.js').read() + main = generated[generated.find('function _main'):] + main = main[:main.find('\n}')] + num_vars = 0 + for v in re.findall('var [^;]+;', main): + num_vars += v.count(',') + 1 + assert num_vars == 10, 'no variable elimination should have been run, but seeing %d' % num_vars + def test_unaligned(self): if Settings.QUANTUM_SIZE == 1: return self.skip('No meaning to unaligned addresses in q1') @@ -8606,12 +8616,6 @@ void*:16 assert ' & 255]()' not in original, 'big function table does not exist' assert ' & 255]()' in final, 'big function table exists' - assert 'asm1' in test_modes - if self.run_name == 'asm1': - assert not Settings.RELOOP - Settings.RELOOP = 1 # check for mixing of relooping with asm1 - self.do_run(path_from_root('tests', 'cubescript'), '*\nTemp is 33\n9\n5\nhello, everyone\n*', main_file='command.cpp') - def test_gcc_unmangler(self): Settings.NAMED_GLOBALS = 1 # test coverage for this @@ -8649,7 +8653,10 @@ void*:16 def test_freetype(self): if self.emcc_args is None: return self.skip('requires emcc') if Settings.QUANTUM_SIZE == 1: return self.skip('TODO: Figure out and try to fix') - if Settings.ASM_JS and '-O2' not in self.emcc_args: return self.skip('mozilla bug 863867') + + assert 'asm2g' in test_modes + if self.run_name == 'asm2g': + Settings.ALIASING_FUNCTION_POINTERS = 1 - Settings.ALIASING_FUNCTION_POINTERS # flip for some more coverage here if Settings.CORRECT_SIGNS == 0: Settings.CORRECT_SIGNS = 1 # Not sure why, but needed diff --git a/tests/test_other.py b/tests/test_other.py index afad1927..264e75e1 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -125,8 +125,6 @@ Options that are modified or new in %s include: (['-o', 'something.js', '-O0'], 0, None, 0, 0), (['-o', 'something.js', '-O1'], 1, None, 0, 0), (['-o', 'something.js', '-O1', '-g'], 1, None, 0, 0), # no closure since debug - (['-o', 'something.js', '-O1', '--closure', '1'], 1, None, 1, 0), - (['-o', 'something.js', '-O1', '--closure', '1', '-s', 'ASM_JS=0'], 1, None, 1, 0), (['-o', 'something.js', '-O2'], 2, None, 0, 1), (['-o', 'something.js', '-O2', '-g'], 2, None, 0, 0), (['-o', 'something.js', '-Os'], 2, None, 0, 1), @@ -169,9 +167,9 @@ Options that are modified or new in %s include: # closure has not been run, we can do some additional checks. TODO: figure out how to do these even with closure assert '._main = ' not in generated, 'closure compiler should not have been run' if keep_debug: - assert ('(label)' in generated or '(label | 0)' in generated) == (opt_level <= 1), 'relooping should be in opt >= 2' + assert ('(label)' in generated or '(label | 0)' in generated) == (opt_level <= 0), 'relooping should be in opt >= 1' assert ('assert(STACKTOP < STACK_MAX' in generated) == (opt_level == 0), 'assertions should be in opt == 0' - assert 'var $i;' in generated or 'var $i_0' in generated or 'var $storemerge3;' in generated or 'var $storemerge4;' in generated or 'var $i_04;' in generated or 'var $original = 0' in generated, 'micro opts should always be on' + assert 'var $i;' in generated or 'var $i_0' in generated or 'var $storemerge3;' in generated or 'var $storemerge4;' in generated or '$i_04' in generated or '$i_05' in generated or 'var $original = 0' in generated, 'micro opts should always be on' if opt_level >= 2 and '-g' in params: assert re.search('HEAP8\[\$?\w+ ?\+ ?\(+\$?\w+ ?', generated) or re.search('HEAP8\[HEAP32\[', generated), 'eliminator should create compound expressions, and fewer one-time vars' # also in -O1, but easier to test in -O2 assert ('_puts(' in generated) == (opt_level >= 1), 'with opt >= 1, llvm opts are run and they should optimize printf to puts' @@ -1647,6 +1645,8 @@ f.close() ['asm', 'outline']), (path_from_root('tools', 'test-js-optimizer-asm-outline3.js'), open(path_from_root('tools', 'test-js-optimizer-asm-outline3-output.js')).read(), ['asm', 'outline']), + (path_from_root('tools', 'test-js-optimizer-asm-minlast.js'), open(path_from_root('tools', 'test-js-optimizer-asm-minlast-output.js')).read(), + ['asm', 'minifyWhitespace', 'last']), ]: print input output = Popen(listify(NODE_JS) + [path_from_root('tools', 'js-optimizer.js'), input] + passes, stdin=PIPE, stdout=PIPE).communicate()[0] @@ -1666,20 +1666,16 @@ f.close() try: os.environ['EMCC_DEBUG'] = '1' os.environ['EMCC_CORES'] = '2' # standardize over machines - for asm, linkable, chunks, js_chunks in [ - (0, 0, 2, 2), (0, 1, 2, 4), - (1, 0, 2, 2), (1, 1, 2, 4) + for asm, linkable, chunks in [ + (0, 0, 2), (0, 1, 2), + (1, 0, 2), (1, 1, 2) ]: - print asm, linkable, chunks, js_chunks + print asm, linkable, chunks output, err = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_libcxx.cpp'), '-O1', '-s', 'LINKABLE=%d' % linkable, '-s', 'ASM_JS=%d' % asm] + (['-O2'] if asm else []), stdout=PIPE, stderr=PIPE).communicate() ok = False for c in range(chunks, chunks+2): ok = ok or ('phase 2 working on %d chunks' % c in err) assert ok, err - ok = False - for c in range(js_chunks, js_chunks+2): - ok = ok or ('splitting up js optimization into %d chunks' % c in err) - assert ok, err finally: del os.environ['EMCC_DEBUG'] del os.environ['EMCC_CORES'] diff --git a/tests/test_sanity.py b/tests/test_sanity.py index aa3f1242..a0fff252 100644 --- a/tests/test_sanity.py +++ b/tests/test_sanity.py @@ -429,12 +429,12 @@ fi stdout=PIPE, stderr=PIPE).communicate() self.assertContained('hello, world!', run_js('a.out.js')) output = '\n'.join(output) - assert ('bootstrapping relooper succeeded' in output) == (i == 2), 'only bootstrap on first O2: ' + output - assert os.path.exists(RELOOPER) == (i >= 2), 'have relooper on O2: ' + output + assert ('bootstrapping relooper succeeded' in output) == (i == 1), 'only bootstrap on first O2: ' + output + assert os.path.exists(RELOOPER) == (i >= 1), 'have relooper on O2: ' + output src = open('a.out.js').read() main = src.split('function _main()')[1].split('\n}\n')[0] - assert ('while (1) {' in main or 'while(1){' in main or '} while ($' in main or '}while($' in main) == (i >= 2), 'reloop code on O2: ' + main - assert ('switch' not in main) == (i >= 2), 'reloop code on O2: ' + main + assert ('while (1) {' in main or 'while(1){' in main or 'while(1) {' in main or '} while ($' in main or '}while($' in main) == (i >= 1), 'reloop code on O2: ' + main + assert ('switch' not in main) == (i >= 1), 'reloop code on O2: ' + main def test_jcache(self): PRE_LOAD_MSG = 'loading pre from jcache' diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index e5e0d287..022bdf47 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -3788,11 +3788,27 @@ function asmLastOpts(ast) { node[1] = simplifyNotCompsDirect(['unary-prefix', '!', conditionToBreak]); return node; } - } else if (type == 'binary' && node[1] == '&' && node[3][0] == 'unary-prefix' && node[3][1] == '-' && node[3][2][0] == 'num' && node[3][2][1] == 1) { - // Change &-1 into |0, at this point the hint is no longer needed - node[1] = '|'; - node[3] = node[3][2]; - node[3][1] = 0; + } else if (type == 'binary') { + if (node[1] === '&') { + if (node[3][0] === 'unary-prefix' && node[3][1] === '-' && node[3][2][0] === 'num' && node[3][2][1] === 1) { + // Change &-1 into |0, at this point the hint is no longer needed + node[1] = '|'; + node[3] = node[3][2]; + node[3][1] = 0; + } + } else if (node[1] === '-' && node[3][0] === 'unary-prefix') { + // avoid X - (-Y) because some minifiers buggily emit X--Y which is invalid as -- can be a unary. Transform to + // X + Y + if (node[3][1] === '-') { // integer + node[1] = '+'; + node[3] = node[3][2]; + } else if (node[3][1] === '+') { // float + if (node[3][2][0] === 'unary-prefix' && node[3][2][1] === '-') { + node[1] = '+'; + node[3][2] = node[3][2][2]; + } + } + } } }); }); diff --git a/tools/shared.py b/tools/shared.py index b3c3257b..c816f091 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -758,7 +758,6 @@ class Settings2(type): self.attrs['ASM_JS'] = 1 self.attrs['ASSERTIONS'] = 0 self.attrs['DISABLE_EXCEPTION_CATCHING'] = 1 - if opt_level >= 2: self.attrs['RELOOP'] = 1 self.attrs['ALIASING_FUNCTION_POINTERS'] = 1 if opt_level >= 3: @@ -1425,11 +1424,11 @@ class Building: emcc_leave_inputs_raw = os.environ.get('EMCC_LEAVE_INPUTS_RAW') if emcc_leave_inputs_raw: del os.environ['EMCC_LEAVE_INPUTS_RAW'] - def make(opt_level): + def make(opt_level, reloop): raw = relooper + '.raw.js' Building.emcc(os.path.join('relooper', 'Relooper.cpp'), ['-I' + os.path.join('relooper'), '--post-js', os.path.join('relooper', 'emscripten', 'glue.js'), - '--memory-init-file', '0', + '--memory-init-file', '0', '-s', 'RELOOP=%d' % reloop, '-s', 'EXPORTED_FUNCTIONS=["_rl_set_output_buffer","_rl_make_output_buffer","_rl_new_block","_rl_delete_block","_rl_block_add_branch_to","_rl_new_relooper","_rl_delete_relooper","_rl_relooper_add_block","_rl_relooper_calculate","_rl_relooper_render", "_rl_set_asm_js_mode"]', '-s', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=["memcpy", "memset", "malloc", "free", "puts"]', '-s', 'RELOOPER="' + relooper + '"', @@ -1444,10 +1443,10 @@ class Building: # bootstrap phase 1: generate unrelooped relooper, for which we do not need a relooper (so we cannot recurse infinitely in this function) logging.info(' bootstrap phase 1') - make(1) + make(2, 0) # bootstrap phase 2: generate relooped relooper, using the unrelooped relooper (we see relooper.js exists so we cannot recurse infinitely in this function) logging.info(' bootstrap phase 2') - make(2) + make(2, 1) logging.info('bootstrapping relooper succeeded') logging.info('=======================================') ok = True diff --git a/tools/test-js-optimizer-asm-minlast-output.js b/tools/test-js-optimizer-asm-minlast-output.js new file mode 100644 index 00000000..d25c6d9b --- /dev/null +++ b/tools/test-js-optimizer-asm-minlast-output.js @@ -0,0 +1,2 @@ +function test($34){var $35=0;$35=$34+130.0;$35=$34+130;return $35|0} + diff --git a/tools/test-js-optimizer-asm-minlast.js b/tools/test-js-optimizer-asm-minlast.js new file mode 100644 index 00000000..6d172899 --- /dev/null +++ b/tools/test-js-optimizer-asm-minlast.js @@ -0,0 +1,8 @@ +function test($34) { + var $35 = 0; + $35=($34)-((+-130)); + $35=($34)-(-130); + return $35 | 0; +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["test"] + |