aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xemcc29
-rwxr-xr-xemscripten.py2
-rw-r--r--src/analyzer.js97
-rw-r--r--src/compiler.js11
-rw-r--r--src/intertyper.js25
-rw-r--r--src/jsifier.js2
-rw-r--r--src/library_browser.js1
-rw-r--r--src/modules.js6
-rw-r--r--src/parseTools.js14
-rw-r--r--src/postamble.js1
-rw-r--r--tests/test_core.py23
-rw-r--r--tests/test_other.py20
-rw-r--r--tests/test_sanity.py8
-rw-r--r--tools/js-optimizer.js26
-rw-r--r--tools/shared.py9
-rw-r--r--tools/test-js-optimizer-asm-minlast-output.js2
-rw-r--r--tools/test-js-optimizer-asm-minlast.js8
17 files changed, 191 insertions, 93 deletions
diff --git a/emcc b/emcc
index ff81e424..f8d7319e 100755
--- a/emcc
+++ b/emcc
@@ -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"]
+